import { Time } from "koishi"
import fsp from "fs/promises"
import { resolve } from "path"
import { inspect } from "util"

export const name = "-lnnbot-command-logger"

export async function apply(ctx) {
  const path = resolve(ctx.baseDir, "data/lnnbot-command-logs")
  await fsp.mkdir(path, { recursive: true })

  const history = []

  const q = []
  let w = false
  async function log(m) {
    ctx.logger.debug(m)
    history.push(m)
    if (history.length > 10000) history.splice(0, history.length - 8000)

    q.push(m)
    if (w) return
    w = true
    try {
      while (q.length)
        await fsp.appendFile(
          resolve(path, Time.template("yyyy-MM-dd") + ".log"),
          q.shift() + "\n",
        )
    } finally {
      w = false
    }
  }

  function escape(str) {
    return str
      .replace(/\\/g, "\\\\")
      .replace(/\x08/g, "\\b")
      .replace(/\t/g, "\\t")
      .replace(/\n/g, "\\n")
      .replace(/\f/g, "\\f")
      .replace(/\r/g, "\\r")
      .replace(/\p{C}+/gu, p => Array.from({ length: p.length }, (_, i) => `\\u${p.charCodeAt(i).toString(16).padStart(4, "0")}`).join(""))
      .replace(/(data:[^;,]*;base64,|base64:)[A-Za-z0-9+/]{1000,}={0,2}/g, (u, p) => u.slice(0, p.length + 996) + '\\[...\\]')
  }
  function quote(str, force) {
    if (!str) return '""'
    if (!force && !/^-|[\s"']/.test(str)) return escape(str)
    if (~~str.match(/"/g)?.length > ~~str.match(/'/g)?.length)
      return `'${escape(str).replace(/\x27/g, "\\\x27")}'`
    return `"${escape(str).replace(/\x22/g, "\\\x22")}"`
  }
  function formatValue(val, type) {
    if (["image", "video", "audio"].includes(type)) return formatAttachment(type, val)
    if (["el", "elements", "fragment"].includes(type)) val = val.join("")
    if (typeof val !== "string") return inspect(val, { compact: true, breakLength: Infinity })
    if (ctx.$commander.resolveDomain(type)?.greedy) return escape(val)
    return quote(val)
  }
  function formatAttachment(type, attrs) {
    let c = String(type)
    for (const [k, v] of Object.entries(attrs))
      c += v === true ? ` ${k}` : v === false ? ` no-${k}` : ` ${k}=${quote(v, true)}`
    return `<${c}/>`
  }
  function formatCommand(argv) {
    //if (argv.root || argv.initiator === "$(") return esc(argv.source)
    let c = argv.command.displayName
    for (let [k, v] of Object.entries(argv.options)) {
      if (v === argv.command._options[k]?.fallback) continue
      if (v === false) {
        k = `no-${k}`
        v = true
      }
      c += ` ${k.length > 1 ? "--" : "-"}${k}`
      if (v !== true) c += `=${formatValue(v, argv.command._options[k]?.type)}`
    }
    argv.args.forEach((v, i) => {
      let arg = argv.command._arguments[i]
      if (!arg && argv.command._arguments.at(-1)?.variadic) arg = argv.command._arguments.at(-1)
      c += " " + formatValue(v, arg?.type)
    })
    return c
  }

  ctx.before("command/execute", argv => (async () => {
    try {
      const { session } = argv
      const t = Time.template("yyyy-MM-dd hh:mm:ss.SSS")
      const b = session.sid
      const c = session.isDirect ? "private" : "#" + session.channelId
      const u = "@" + session.userId
      const m = session.messageId
      const a = argv.root === true ? "/" : argv.root === "-lnnbot-pseudo" ? "->" : argv.initiator || ">"
      const s = formatCommand(argv)
      await log(`${t} ${b} ${c} ${u} ${m} ${a} ${s}`)
    } catch (err) {
      ctx.logger.error(err)
    }
  })())

  ctx.on("whatlang/run", (code, session) => (async () => {
    try {
      const t = Time.template("yyyy-MM-dd hh:mm:ss.SSS")
      const b = session.sid
      const c = session.isDirect ? "private" : "#" + session.channelId
      const u = "@" + session.userId
      const m = session.messageId
      const s = escape(code.trimStart())
      await log(`${t} ${b} ${c} ${u} ${m} ¿ ${s}`)
    } catch (err) {
      ctx.logger.error(err)
    }
  })())

  ctx.inject(["console"], ctx => {
    ctx.console.addListener("get-lnnbot-command-logs", () => {
      return history
    }, { authority: 2 })
    ctx.console.addEntry(resolve(ctx.baseDir, "assets/console/lnnbot-command-logger.js"))
  })
}
